package org.trc.traffic.analysis;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.swing.JFileChooser;

import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.shapefile.shp.ShapefileException;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.GeoTools;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.filter.text.cql2.CQL;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.swing.data.JFileDataStoreChooser;

import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;

import org.trc.io.DBAccess;
import org.trc.network.Network;
import org.trc.network.Node;
import org.trc.network.RoutePlanning;
import org.trc.traffic.AggLinkTraffic;

import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.Point;

public class STCongestionClustering {
	double timeCost = 0;
	
	//输入
	protected float maxDistance;

	protected int maxTimeInterval;
	
	//聚簇结果
	protected Map<String, CongestionCluster> congestionClusterSet;

	//辅助
	protected Network road = null; //(几何、拓扑、线性参照)
 
	private HashMap<String, Point> pointMap = new HashMap<String, Point>();

	private String roadShapefileURL = "D:\\Data\\Road\\Wuhan_2010.9\\WuhanMainRoad_3rd_PATHNAME_wuchang6_EPSG32650.shp";
	
	private String spatialShapefielURL = "D:\\Data\\Road\\Wuhan_2010.9\\WuhanRoadCentroid_UTM50N\\WuhanRoadCentroid_UTM50N.shp";
	
	//-------------------------------------------聚簇------------------------------------//
	
	//数据初始化 
	public void initial() {	
		try {
			this.road = new Network(roadShapefileURL, true);
			
			this.bulidPointMap();	// 路网边中心点初始化	
			
		} catch (Exception e) {
			e.printStackTrace();
		}  		
	}

	/**
	 * 依据不同的空间粒度类型，进行数据集的获取，然后开始聚簇
	 * 
	 * @param stype : 空间类型  1：路网   2：道路   3:路段
	 * @param sid
	 * @param startHour
	 * @param endHour
	 * @param day
	 */
	public void buildCongestionCluster(int stype, String sid, int startHour, int endHour, Date day, float maxDistance, int maxTimeInterval) {
		this.initial();
		
		timeCost = System.currentTimeMillis();
		
		this.maxDistance = maxDistance;
		
		this.maxTimeInterval = maxTimeInterval;
		
		//准备数据
		List<AggLinkTraffic> linkTrafficDataSet = DBAccess.getLinkTraffic(1, 12, startHour, endHour, day, 0);
		
		this.congestionClusterSet = Collections.synchronizedMap(new HashMap<String, CongestionCluster>());
		
		//分组
		int clusterNum = 1;
		for (int i = 0; i < linkTrafficDataSet.size(); i++) {
			if (linkTrafficDataSet.get(i).clusterID == null) {// 未分组  
				this.expandCongestionCluster(linkTrafficDataSet.get(i), linkTrafficDataSet, sid, clusterNum);
				clusterNum++;
			}
		}
		
		//生成 cluster		
		CommonCongestionCluster congestionCluster = null;
		for (AggLinkTraffic linkTraffic : linkTrafficDataSet) {
			String clusterID = linkTraffic.clusterID;
			if (!congestionClusterSet.containsKey(clusterID)) {
				congestionCluster = new CommonCongestionCluster(clusterID);
				congestionClusterSet.put(clusterID, congestionCluster);
			}
			congestionCluster = (CommonCongestionCluster)congestionClusterSet.get(clusterID);
			congestionCluster.add(linkTraffic);
		}
		
		timeCost =  System.currentTimeMillis() - this.timeCost;
	}
				
	// 迭代扩展
	protected void expandCongestionCluster(AggLinkTraffic coreLinkTraffic, List<AggLinkTraffic> linkTrafficDataSet, String sid, int clusterNum) {
		String clusterID = sid + "_" + Integer.toString(clusterNum); 
		
		coreLinkTraffic.setClusterID(clusterID);
		
		List<AggLinkTraffic> resultDataSet = this.STRangeQuery(coreLinkTraffic, linkTrafficDataSet);
		
		if(resultDataSet.size() == 0)
			return;
		
		for (AggLinkTraffic trafficLink : resultDataSet) {// 标记簇ID
			if(trafficLink.clusterID == null)
				expandCongestionCluster(trafficLink, linkTrafficDataSet, sid, clusterNum);
		}   
		
	}

	// 时空领域查询
	private List<AggLinkTraffic> STRangeQuery(AggLinkTraffic seed, List<AggLinkTraffic> linkTrafficDataSet) { // 通过遍历
		Vector<AggLinkTraffic> stRange_List = new Vector<AggLinkTraffic>();
		RoutePlanning rp = new RoutePlanning(this.road);
		for (AggLinkTraffic dataObject : linkTrafficDataSet) {
			//if (timeWithin(seed, dataObject) && (spatialWithinBySP(seed, dataObject, rp ) || spatialWithinBySP( dataObject, seed, rp)))
			if (timeWithin(seed, dataObject)&& spatialWithin(seed, dataObject))	
			 stRange_List.add(dataObject);
		}
		return stRange_List;
	}

	// 时间间隔
	protected boolean timeWithin(AggLinkTraffic linkTraffic1, AggLinkTraffic linkTraffic2) {
		int linkTimeStamp1 = linkTraffic1.hour *12 + linkTraffic1.timeInterval;
		int linkTimeStamp2 = linkTraffic2.hour *12 + linkTraffic2.timeInterval;
		
		if (Math.abs(linkTimeStamp1 - linkTimeStamp2) > this.maxTimeInterval) {
			return false;
		} else {
			return true;
		}
	}

	// 空间距离
	protected boolean spatialWithin(AggLinkTraffic seed, AggLinkTraffic dataObject) {
		Point point1 = pointMap.get(seed.linkID);
		Point point2 = pointMap.get(dataObject.linkID);

		if (point1 == null || point2 == null)
			return false;

		if (point1.distance(point2) < this.maxDistance) {// System.out.println("We are near the same zone");
			return true;
		} else
			return false;
	}
	
	protected boolean spatialWithinBySP(AggLinkTraffic startLinkTraffic, AggLinkTraffic endLinkTraffic, RoutePlanning rp) {
		if(startLinkTraffic.linkID.equals(endLinkTraffic.linkID))
			return true;
		
		Node startNode = null;
		
		Node endNode = null;
		
		if(road.getEdgeById(startLinkTraffic.linkID).direction == 3 )
			startNode = road.getNodeById(road.getEdgeById(startLinkTraffic.linkID).endNodeID);
		else
			startNode = road.getNodeById(road.getEdgeById(startLinkTraffic.linkID).startNodeID);
		
		if(road.getEdgeById(endLinkTraffic.linkID).direction == 3)
			endNode = road.getNodeById(road.getEdgeById(endLinkTraffic.linkID).startNodeID);
		else 
			endNode = road.getNodeById(road.getEdgeById(endLinkTraffic.linkID).endNodeID);
		
		rp.computeFastestWay(startNode, endNode);
		
		if(rp.getRouteLength()< this.maxDistance)
			return true;
		else 
			return false;
	}
	
	//-------------------------------------------输出------------------------------------//
	// 获取簇
	public CongestionCluster[] getCongestionCluster() {
		CongestionCluster[] congestionClusterList = new CongestionCluster[congestionClusterSet.size()];

		congestionClusterSet.values().toArray(congestionClusterList);

		return congestionClusterList;
	}

	// 生成包含所有重要时空簇的SHAPEFILE（点图）
	public void getVIPCongestionClusterMap() throws IOException,MismatchedDimensionException, TransformException, NoSuchAuthorityCodeException, FactoryException {
		// specify the shape file to save
		JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
		chooser.setDialogTitle("Save a new shapefile for muliple congestion clusters");
		chooser.setSaveFile(new File("C:\\VIPCongestionCluster.shp"));

		if (chooser.showSaveDialog(null) != JFileChooser.APPROVE_OPTION) {
			return;
		}

		File outputShapefile = chooser.getSelectedFile();

		ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
		Map<String, Serializable> params = null;
		ShapefileDataStore writeDataStore = null;
		SimpleFeatureType TYPE = null;

		try {
			params = new HashMap<String, Serializable>();
			params.put("url", outputShapefile.toURI().toURL());
			params.put("create spatial index", Boolean.TRUE);

			writeDataStore = (ShapefileDataStore) dataStoreFactory
					.createNewDataStore(params);
			TYPE = DataUtilities.createType("SamplePoint",
					"GEOM:Point:srid=32650," + "ID:String," + "DUR:int," + "CLUSTERID:String");

			writeDataStore.createSchema(TYPE);
			writeDataStore.forceSchemaCRS(CRS.decode("EPSG:32650", true));

		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (SchemaException e) {
			e.printStackTrace();
		}

		// prepare new feature collection
		SimpleFeatureCollection collection = FeatureCollections.newCollection();
		SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
		SimpleFeature feature = null;
		Point point = null;

		//MathTransform transform = this.getTransformFromEpsgCode("EPSG:32650", "EPSG:4326");

		CommonCongestionCluster commonCluster = null;
		String[] linkIDs = null;

		String[] clusterIDs = new String[congestionClusterSet.size()];
		congestionClusterSet.keySet().toArray(clusterIDs);
				
		for (String clusterID : clusterIDs) {
			commonCluster = (CommonCongestionCluster)congestionClusterSet.get(clusterID);
			int linkNum = commonCluster.linkNum;
			int dur = commonCluster.aggDur;
			if (linkNum >= 2 || dur >= 15) {
				linkIDs = commonCluster.getLinkIDList();
				for (String linkID : linkIDs) {
					//point = (Point) JTS.transform(this.pointMap.get(linkID),transform);
					point = this.pointMap.get(linkID);
					// create feature
					featureBuilder.add(point);
					featureBuilder.add(linkID);
					featureBuilder.add(commonCluster.getLinkAggDur(linkID));
					featureBuilder.add(commonCluster.clusterID);
					feature = featureBuilder.buildFeature(null);
					collection.add(feature);
				}
			}
		}

		// create transaction
		Transaction transaction = new DefaultTransaction("create");
		String typeName = writeDataStore.getTypeNames()[0];
		SimpleFeatureSource featureSource = writeDataStore
				.getFeatureSource(typeName);

		// create shape file
		if (featureSource instanceof SimpleFeatureStore) {
			SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
			featureStore.setTransaction(transaction);
			try {
				featureStore.addFeatures(collection);
				transaction.commit();
			} catch (Exception problem) {
				problem.printStackTrace();
				transaction.rollback();
			} finally {
				transaction.close();
			}
			System.exit(0); // success!
		} else {
			System.out
					.println(typeName + " does not support read/write access");
		}

	}
	
	//-----------------------------------------私有方法----------------------------------//	
	// 将几何属性放入HashMap
	private void bulidPointMap(){
		String id = null;
		Point point = null;
		try {
			FileDataStore dataStore = FileDataStoreFinder.getDataStore(new File(spatialShapefielURL));
			SimpleFeatureSource featureSource = dataStore.getFeatureSource();
			SimpleFeatureCollection sfc = featureSource.getFeatures();
			SimpleFeatureIterator iterator = sfc.features();
			SimpleFeature sf = null;
			while (iterator.hasNext()) {
				sf = iterator.next();
				id = (String) sf.getAttribute("ID");
				point = (Point) sf.getDefaultGeometry();
				pointMap.put(id, point);
			}
			iterator.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	// 坐标转换
	private MathTransform getTransformFromEpsgCode(String epsgCode1,
			String epsgCode2) {
		CoordinateReferenceSystem CRS1 = null;
		CoordinateReferenceSystem CRS2 = null;
		MathTransform mathTransform = null;
		try {
			CRS1 = CRS.decode(epsgCode1, true);
			CRS2 = CRS.decode(epsgCode2, true);
			boolean lenient = true;
			mathTransform = CRS.findMathTransform(CRS1, CRS2, lenient);
		} catch (NoSuchAuthorityCodeException e) {
			e.printStackTrace();
		} catch (FactoryException e) {
			e.printStackTrace();
		}
		return mathTransform;
	}
	
	public static void main(String[] args) throws ParseException, MismatchedDimensionException, IOException, TransformException,
			NoSuchAuthorityCodeException, FactoryException {

		STCongestionClustering stCongestionClustering = new STCongestionClustering();
		
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd");
		
		stCongestionClustering.buildCongestionCluster(3, "Network", 8, 8, dateFormat.parse("2008-12-28"), 300, 1);

		System.out.println("Time cost : " + stCongestionClustering.timeCost);
		
		//System.out.print("Record Num:" );
		//System.out.print("Cluster Num:");

		// 遍历簇
		CongestionCluster[] congestionClusters = stCongestionClustering.getCongestionCluster();
		
		for (CongestionCluster congestionCluster : congestionClusters) {
			CommonCongestionCluster cc = (CommonCongestionCluster)congestionCluster;
			System.out.print("CluterID: " + cc.clusterID + ",");
			System.out.print(" LinkNum: " + cc.linkNum + ",");
			System.out.print(" AggDur: " + cc.aggDur);
			System.out.println();
		}

		System.out.println("Creating the CongestionCluterMap");

		stCongestionClustering.getVIPCongestionClusterMap();

		/*
		 * for(LinkTraffic linkTraffic :
		 * stCongestionCluster.linkTrafficDataSet){
		 * System.out.println(linkTraffic.linkID + ","+ linkTraffic.clusterID);
		 * }
		 */
	}

}